/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-void */
import { localStorage } from "./storage";

export const isObject = (target: any): boolean => {
    return Object.prototype.toString.call(target) === "[object Object]";
};
export const isEmptyObject = (obj: object): boolean => {
    for (const key in obj) {
        return false;
    }
    return true;
};
// 合并对象
export function merge<T>(...arg: any[]): T {
    if (arg.length === 0) {
        throw Error("merge error=>请传入需要合并的对象");
    }
    let target = arg[0] || {};
    let depath = false;
    const length = arg.length;
    let i = 1;

    if (Object.prototype.toString.call(target) === "[object Boolean]") {
        depath = Boolean(target);
        target = arg[i];
        i++;
    }

    if (typeof target !== "object") {
        target = {};
    }

    if (i === 2 && length <= 1) {
        throw Error("merge error=>请传入需要合并的对象");
    }

    for (; i < length; i++) {
        const source = arg[i] || {};
        if (source !== null) {
            for (const key in source) {
                const src = target[key];
                const copy = source[key];
                if (target === copy) {
                    continue;
                }
                if (Object.prototype.hasOwnProperty.call(source, key)) {
                    if (
                        depath &&
                        copy &&
                        (isObject(copy) || Array.isArray(copy))
                    ) {
                        let clone;
                        if (Array.isArray(copy)) {
                            clone = src && Array.isArray(src) ? src : [];
                        } else {
                            clone = src && isObject(src) ? src : {};
                        }
                        target[key] = merge(depath, clone, copy);
                    } else if (copy !== void 0) {
                        target[key] = copy;
                    }
                }
            }
        }
    }
    return target;
}

/**
 * 将数字格式化为金额显示格式（100,000.00）
 * @Author   陈龙
 * @DateTime 2017-08-30
 * @version  [version]
 * @param    {[type]}   num 值    例: 250000.01
 * @return   {[type]}   返回金额格式字符串      250,000.01
 */

export const amountFormat = (num: string|number, opt?: object) => {
    let _option: {
        [key:string]: any
    } = {
        decimalNum: 2,
        appendZero: true,
        step: ","
    };
    if (opt) {
        _option = merge(true, _option, opt);
    }
    const nums: string[] = (num || 0)
        .toString()
        // eslint-disable-next-line no-useless-escape
        .replace(/\,/g, "")
        .split(".");
    let result = "";
    while (nums[0].length > 3) {
        result = _option.step + "" + nums[0].slice(-3) + result;
        nums[0] = nums[0].slice(0, nums[0].length - 3);
    }
    if (nums[0]) {
        result = nums[0] + result;
    }
    if (_option.appendZero) {
        nums[1] = nums[1] || "";
        result += "." + (nums[1] + "00").substring(0, _option.decimalNum);
    }
    return result;
};

export const trim = (payload: string): string => {
    let str = payload;
    const whitespace =
        " \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000";
    for (let i = 0, len = str.length; i < len; i++) {
        if (whitespace.indexOf(str.charAt(i)) === -1) {
            str = str.substring(i);
            break;
        }
    }
    for (let i = str.length - 1; i >= 0; i--) {
        if (whitespace.indexOf(str.charAt(i)) === -1) {
            str = str.substring(0, i + 1);
            break;
        }
    }
    return whitespace.indexOf(str.charAt(0)) === -1 ? str : "";
    // return payload == null ? "" : (payload + "").replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
};

export const getTransform = () => {
    let transform = "";
    // document.createElement() 创建元素节点，
    const divStyle = document.createElement("div").style; // 返回的是一组object对象，里面包含了transform
    // 可能涉及到的几种兼容性写法，通过循环，找出浏览器识别的那个
    // 依次为： 全部、Safari 和 Chrome、firefox、ie9、opera
    const transformArr = [
        "transform",
        "webkitTransform",
        "MozTransform",
        "msTransform",
        "OTransform"
    ];
    for (let i = 0; i < transformArr.length; i++) {
        if (transformArr[i] in divStyle) {
            // 找到以后立刻返回，结束函数
            return (transform = transformArr[i]);
        }
    }
    return transform;
};

export const debounce = function(func: any, delay = 300) {
    let timer: any;
    return function(...arg: any[]) {
        const args = arg;
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
            func(...args);
        }, delay);
    };
};

export const throttle = function(func: any, delay = 300) {
    let timer: any;
    let needRun = true;
    return function(...arg: any[]) {
        if (!needRun) return;
        needRun = false;
        const args = arg;
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
            needRun = true;
            func(...args);
        }, delay);
    };
};
interface ToolHandler{
  (...arg: any[]): void;
}
export const detectOrient = function(handlerHorizontal: ToolHandler, handlerVertical: ToolHandler) {
    const data: any = localStorage.getItem("J-recordOrientX");
    const cw = document.documentElement.clientWidth;
    let _Width = 0;
    let _Height = 0;
    if (!data) {
        const sw = window.screen.width;
        const sh = window.screen.height;

        // 2.在某些机型（如华为P9）下出现 srceen.width/height 值交换，所以进行大小值比较判断
        _Width = sw < sh ? sw : sh;
        _Height = sw >= sh ? sw : sh;
        localStorage.setItem("J-recordOrientX", { w: _Width, h: _Height });
    } else {
        _Width = data.w;
        _Height = data.h;
    }
    if (cw === _Width) {
        // 竖屏
        handlerVertical && handlerVertical();
        return;
    }
    if (cw === _Height) {
        // 横屏
        handlerHorizontal && handlerHorizontal();
    }
};

// swiper的观察者模式
export class ObserverClass {
    params: any = {};

    eventsListeners: any = {};
    constructor(params?: any) {
        const self = this;
        if (params) {
            self.params = params;
        }
        // Events
        self.eventsListeners = {};
        if (self.params && self.params.on) {
            Object.keys(self.params.on).forEach(eventName => {
                self.on(eventName, self.params.on[eventName]);
            });
        }
    }

    on(events: string, handler: any, priority?: boolean) {
        const self = this;
        if (typeof handler !== "function") return self;
        const method = priority ? "unshift" : "push";
        events.split(" ").forEach(event => {
            if (!self.eventsListeners[event]) self.eventsListeners[event] = [];
            self.eventsListeners[event][method](handler);
        });
        return self;
    }

    off(events: string, handler: any) {
        const self = this;
        if (!self.eventsListeners) return self;
        events.split(" ").forEach(event => {
            if (typeof handler === "undefined") {
                self.eventsListeners[event] = [];
            } else {
                self.eventsListeners[event].forEach((eventHandler: any, index: any) => {
                    if (eventHandler === handler) {
                        self.eventsListeners[event].splice(index, 1);
                    }
                });
            }
        });
        return self;
    }

    emit(...args: any) {
        const self = this;
        if (!self.eventsListeners) return self;
        let events;
        let data: any;
        let context: any;
        if (typeof args[0] === "string" || Array.isArray(args[0])) {
            events = args[0];
            data = args.slice(1, args.length);
            context = self;
        } else {
            events = args[0].events;
            data = args[0].data;
            context = args[0].context || self;
        }
        const eventsArray = Array.isArray(events) ? events : events.split(" ");
        eventsArray.forEach((eventName: any) => {
            if (self.eventsListeners && self.eventsListeners[eventName]) {
                const handlers: any[] = [];
                self.eventsListeners[eventName].forEach((eventHandler: any) => {
                    handlers.push(eventHandler);
                });
                handlers.forEach(eventHandler => {
                    eventHandler.apply(context, data);
                });
            }
        });
        return self;
    }
}

export const ObserverEvent = (el: any, eventType: string, fn: ToolHandler, options?: any) => {
    el.addEventListener(eventType, fn, options || false);
    function destroyObserver() {
        el.removeEventListener(eventType, fn);
    }
    return {
        destroy: destroyObserver
    };
};

export const funcEaseOut = (position: any, destination: any, rate: any, callback?: ToolHandler) => {
    let stepId: number | null = null;
    if (position === destination || typeof destination !== "number") {
        callback && callback(destination, true);
        return false;
    }
    destination = destination || 0;
    rate = rate || 2;
    const step = function() {
        stepId && cancelAnimationFrame(stepId);
        position = position + (destination - position) / rate;
        if (Math.abs(destination - position) < rate) {
            callback && callback(destination, true);
            return;
        }
        callback && callback(position, false);
        stepId = requestAnimationFrame(step);
    };
    step();
};
if (!document.body.scrollTo) {
    Element.prototype.scrollTo = function(opt?: any) {
        if (opt.left) {
            this.scrollLeft = opt.left;
        }
        if (opt.top) {
            this.scrollTop = opt.top;
        }
    };
}
export const scrollTopSmooth = (
    position: any,
    el: any = window,
    needTransition = true,
    rate = 5
) => {
    // 当前滚动高度
    return new Promise((resolve, reject) => {
        const scrollTop = el.scrollTop || document.body.scrollTop;
        if (!needTransition) {
            el.scrollTo && el.scrollTo(0, position);
            resolve(true);
            return;
        }
        funcEaseOut(scrollTop, position, rate, function(
            val,
            animateEnd,
            isError
        ) {
            el.scrollTo && el.scrollTo(0, val);
            if (animateEnd) {
                resolve(animateEnd);
            }
            if (isError) {
                reject(new Error("错"));
            }
        });
    });
};


/**
 * 格式化时间函数
 * @param {format} 时间显示格式
 */
// eslint-disable-next-line no-extend-native
Date.prototype.format = function(format) {
    const date: any = {
        "M+": this.getMonth() + 1,
        "d+": this.getDate(),
        "h+": this.getHours(),
        "m+": this.getMinutes(),
        "s+": this.getSeconds(),
        "q+": Math.floor((this.getMonth() + 3) / 3),
        "w+": this.getDay()
    };
    if (/(y+)/i.test(format)) {
        format = format.replace(
            RegExp.$1,
            (this.getFullYear() + "").substr(4 - RegExp.$1.length)
        );
    }
    if (/(S+)/.test(format)) {
        const ms: any = this.getMilliseconds();
        format = format.replace(
            RegExp.$1,
            RegExp.$1.length === 1
                ? ms
                : ("000" + ms).substring(("" + ms).length)
        );
    }
    if (/(W+)/.test(format)) {
        const weeks: any = {
            1: "一",
            2: "二",
            3: "三",
            4: "四",
            5: "五",
            6: "六",
            0: "天"
        };
        format = format.replace(RegExp.$1, weeks[date["w+"]]);
    }
    for (const k in date) {
        if (new RegExp("(" + k + ")").test(format)) {
            format = format.replace(
                RegExp.$1,
                RegExp.$1.length === 1
                    ? date[k]
                    : ("00" + date[k]).substr(("" + date[k]).length)
            );
        }
    }
    return format;
};


/**
 * 通过字符串操作将一个数放大或缩小指定倍数
 * @val 被转换的数
 * @scaleNum   放大或缩小的倍数，为正表示小数点向右移动，表示放大；为负反之
 */
export const numScale = (val: string|number, scaleNum: string|number): number => {
    const num = String(val);
    const m = Number(scaleNum);
    let parts = num.toString().split(".");

    const integerLen = parts[0].length;

    const decimalLen = parts[1] ? parts[1].length : 0;
    // 放大，当放大的倍数比原来的小数位大时，需要在数字后面补零
    if (m > 0) {
        // 补多少个零：m - 原始值的小数位数
        let zeros = m - decimalLen;
        while (zeros > 0) {
            zeros -= 1;
            parts.push("0");
        }
        // 缩小，当缩小的倍数比原来的整数位大时，需要在数字前面补零
    } else {
        // 补多少个零：m - 原始值的整数位数
        let zeros = Math.abs(m) - integerLen;
        while (zeros > 0) {
            zeros -= 1;
            parts.unshift("0");
        }
    }

    // 小数点位置，也是整数的位数:
    //    放大：原始值的整数位数 + 放大的倍数
    //    缩小：原始值的整数位数 - 缩小的倍数
    const index = integerLen + m;
    // 将每一位都拆到数组里，方便插入小数点
    parts = parts.join("").split("");
    // 当为缩小时，因为可能会补零，所以使用原始值的整数位数
    // 计算出的小数点位置可能为负，这个负数应该正好是补零的
    // 个数，所以小数点位置应该为 0
    parts.splice(index > 0 ? index : 0, 0, ".");

    return parseFloat(parts.join(""));
};

/**
 * 获取小数位数
 */
export const getExponent = (num: string|number) => (Math.floor(Number(num)) === num || !num.toString().split(".")[1] ? 0 : num.toString().split(".")[1].length);
/**
 * 两数相加
 */
export const askPlus = (num1: string|number, num2: string|number) => {
    const multiple = Math.max(getExponent(num1), getExponent(num2));
    return numScale(numScale(num1, multiple) + numScale(num2, multiple), multiple * -1);
};
/**
 * 两数相减
 */
export const askMinus = (num1: string|number, num2: string|number) => {
    const multiple = Math.max(getExponent(num1), getExponent(num2));
    return numScale(numScale(num1, multiple) - numScale(num2, multiple), multiple * -1);
};
/**
 * 两数相乘
 */
export const askTimes = (num1: string|number, num2: string|number) => {
    const multiple = Math.max(getExponent(num1), getExponent(num2));
    // const l1 = getExponent(num1);
    // const l2 = getExponent(num2);
    // return numScale((numScale(num1, multiple) * numScale(num2, multiple)) / Math.pow(10, l2 + l1),multiple * -1);
    return numScale(numScale(num1, multiple) * numScale(num2, multiple), multiple * 2 * -1);
};
/**
 * 两数相除
 */
export const askDiv = (num1: string|number, num2: string|number) => {
    const multiple = Math.max(getExponent(num1), getExponent(num2));
    // const l1 = getExponent(num1);
    // const l2 = getExponent(num2);
    // return numScale((numScale(num1, multiple) / numScale(num2, multiple)) * Math.pow(10, l2 - l1),multiple * -1);
    return numScale(num1, multiple) / numScale(num2, multiple);
};


export const formatList2Level = (sourceList: any, formatLevel = 2) => {
    let _curList = [];
    const setCurrentList = (list: any, level = 1) => {
        const res = [];
        for (let index = 0; index < list.length; index++) {
            const cur = list[index];
            if (formatLevel === level) {
                res.push({
                    ...cur,
                    children: null
                });
                continue;
            }
            if (
                level <= formatLevel &&
                cur.children &&
                cur.children.length > 0
            ) {
                const _res: any = setCurrentList(cur.children, level + 1);
                if (_res.length > 0) {
                    res.push({
                        ...cur,
                        children: _res
                    });
                }
            }
        }
        return res;
    };
    _curList = setCurrentList(sourceList, 1);
    return _curList;
};

// 高德地图初始化
export const AMapLoad = (k: string) => {
    return new Promise(function(resolve, reject) {
        // if (AMap) {
        //     // eslint-disable-next-line no-undef
        //     console.log("AMap", AMap);
        // }
        // @ts-ignore
        window.initTheMap = function() {
            // @ts-ignore
            resolve(AMap);
        };

        const script = document.createElement('script');
        script.charset = "utf-8";
        script.type = 'text/javascript';
        script.async = true;
        script.src = "https://webapi.amap.com/maps?v=1.4.15&callback=initTheMap&key=" + k;
        script.onerror = function(e) {
            reject(e);
        };
        document.head.appendChild(script);
    });
};

export function isURL(s: string) {
    return /^http[s]?:\/\/.*/.test(s);
}
export function getUUID() {
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
        // const r = "r&0x3" | "0x8";
        const r = 8;
        return (c === "x" ? (Math.random() * 16) | 0 : r).toString(16);
    });
}

export function buildCustomObj(data: any, keyArr: any) {
    const arr: any = [];
    data.forEach((item: any) => {
        const obj: any = {};
        keyArr.forEach((it: any) => {
            obj[it.prop] = item[it.prop];
        });
        arr.push(obj);
    });
    return arr;
}


export const treeToList = (tree: any) => {
    console.log(tree);
};

export const listToTree = (list: any, option?: any) => {
    const _opt: {
        [key: string]: any;
    } = merge(
        {
            value: "value",
            children: "children",
            parentId: "parentId"
        },
        option || {}
    );
    const _list: {
        [key:string]: any
    } = merge(true, [], list);
    const info = _list.reduce((map: any, node: any) => {
        map[node[_opt.value]] = node;
        node[_opt.children] = [];
        return map;
    }, {});
    // console.log(info);
    return _list.filter((node: any) => {
        info[node[_opt.parentId]] && info[node[_opt.parentId]].children.push(node);
        return !node[_opt.parentId];
    });
};

export const treeFind = (tree: any, func: any, option?: any) => {
    const _opt: {
        [key: string]: any;
    } = merge(
        {
            value: "value",
            children: "children",
            parentId: "parentId"
        },
        option || {}
    );
    for (const data of tree) {
        if (func(data)) return data;
        if (data[_opt.children] && data[_opt.children].length > 0) {
            const res: any = treeFind(data[_opt.children], func, _opt);
            if (res) return res;
        }
    }
    return null;
};
const treeFindPathOnce = (
    tree: any[],
    func: any,
    path: any[] = [],
    option: any = {}
) => {
    if (!tree) return [];
    for (const data of tree) {
        path.push(data[option.value]);
        if (func(data)) return path;
        if (data[option.children] && data[option.children].length > 0) {
            const findChildren: any[] = treeFindPathOnce(
                data[option.children],
                func,
                path,
                option
            );
            if (findChildren.length) return findChildren;
        }
        path.pop();
    }
    return [];
};
const treeFindPathMultiple = (
    tree: any[],
    func: any,
    path: any[] = [],
    result: any[] = [],
    option: any = {}
) => {
    if (!tree) return [];
    for (const data of tree) {
        path.push(data[option.value]);
        func(data) && result.push([...path]);
        if (data[option.children] && data[option.children].length > 0) {
            treeFindPathMultiple(
                data[option.children],
                func,
                path,
                result,
                option
            );
        }
        path.pop();
    }
    return result;
};
export const treeFindPath = (tree: any, func: any, option?: any) => {
    const _opt: {
        [key: string]: any;
    } = merge(
        {
            value: "value",
            children: "children",
            parentId: "parentId",
            multiple: false
        },
        option || {}
    );
    let res: any[] = [];
    // console.log(_opt);
    if (_opt.multiple) {
        res = treeFindPathMultiple(tree, func, [], [], _opt);
    } else {
        res = treeFindPathOnce(tree, func, [], _opt);
    }
    return res;
};


/**
 * 给拥有绑定和销毁功能的实例提供一个destroy方法，用于匿名函数的场景
 * @param {Object} context 拥有绑定和销毁功能的实例 比如EventBus
 * @param {String}  name 缓存中的key名称
 * @param {Function} func 需要缓存的方法
 * @param {String} $on 绑定的函数key名称，默认值'$on'
 * @param {String} $off  销毁的函数key名称，默认值'$off'
 * @returns {
 *     destroy: Function//销毁函数
 * }
 */
export const ObserverEventBus = (context: any, name: string, func: ToolHandler, $on = '$on', $off = '$off') => {
    context[$on](name, func);
    return {
        destroy() {
            context[$off](name, func);
        }
    };
};


/**
 * 对函数执行进行追踪
 * @param fn 需要追踪的函数
 * fn(done,runCount,count)
 * done 执行结果，true|false true代表执行完成，false代表还在执行中
 * runCount 指目前已经执行几次
 * count 最大执行次数
 * @param count 需要执行的次数
 */
export const performTrackingOnFunction = (fn: any, count = 1) => {
    if (count < 0) {
        count = 1;
    }
    let _runCount = 0;
    const nextTick = (fn?: any) => {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(true);
                fn && fn();
            }, 0);
        });
    };
    const _runFn = async(trackingDisabled = false) => {
        if (trackingDisabled || _runCount >= count) {
            return true;
        }
        if (_runCount > 0) {
            await nextTick();
        }
        _runCount++;
        const msgSuffix = `执行${_runCount}/${count}`;
        console.log("performTrackingOnFunction:", msgSuffix);
        fn(_runFn, _runCount, count);
        return false;
    };
    _runFn(false);
};
